package org.hdz.testnsd;

import android.content.Context;
import android.content.SharedPreferences;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.FileReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Locale;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author hdz
 * @date 2019/01/05
 *
 * https://blog.csdn.net/huangyabin001/article/details/37568549
 * https://www.2cto.com/kf/201407/318583.html
 * https://www.jianshu.com/p/523003cfd325
 * https://www.jianshu.com/p/42a018fd960c
 * https://developer.android.com/training/connect-devices-wirelessly/nsd#java
 *
 * https://www.ctolib.com/docs-android-training-geek-c-nsd.html
 */
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "TEST_NSD";
    private static final String NSD_SERVICE_NAME = "NsdChat";
    private static final String NSD_SERVER_TYPE = "_adrdm._tcp.";//"_http._tcp.";
    private NsdManager mNsdManager;
    private NsdManager.RegistrationListener mRegistrationListener;
    private String mServerName;
    private NsdManager.DiscoveryListener mDiscoveryListener;
    private NsdServiceInfo mService;
    private NsdManager.ResolveListener mResolverListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int port = 10000;
        try {
            ServerSocket sock = new ServerSocket(0);
            port = sock.getLocalPort();
            sock.close();
        } catch (Exception e) {
            Toast.makeText(getApplicationContext(), "can not set port", Toast.LENGTH_SHORT).show();
        }

        initializeRegistrationListener();
        initializeDiscoveryListener();
        initializeResolveListener();
        registerService(port);


        final EditText et = (EditText)findViewById(R.id.etServiceName);
        et.setText(getServiceType(this));
        Button btn = (Button) findViewById(R.id.btnDiscover);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String str = et.getText().toString();
                if (str.equals("")) {
                    return;
                }
                setServiceType(MainActivity.this, str);
                discoverServices(str);
            }
        });
    }

    @Override
    protected void onDestroy() {
        unInitService();
        super.onDestroy();
    }


    private boolean bListenerUsed = false;
    private void discoverServices(String serviceType) {
        if (bListenerUsed) {
            mNsdManager.stopServiceDiscovery(mDiscoveryListener);
            mDiscoveryListener = null;
            initializeDiscoveryListener();
        }
        bListenerUsed = true;
        mNsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
    }

    /**
     * (1) 注册nsd服务
     */
    public void registerService(int port) {
        NsdServiceInfo serviceInfo = new NsdServiceInfo();
        serviceInfo.setServiceName(NSD_SERVICE_NAME);
        serviceInfo.setPort(port);
        serviceInfo.setServiceType(NSD_SERVER_TYPE);

        mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
        mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
        //mNsdManager.discoverServices(NSD_SERVER_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
    }

    /**
     * (2) 初始化注册监听器
     */
    public void initializeRegistrationListener() {
        mRegistrationListener = new NsdManager.RegistrationListener() {
            @Override
            public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {

            }

            @Override
            public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {

            }

            @Override
            public void onServiceRegistered(NsdServiceInfo serviceInfo) {
                mServerName = serviceInfo.getServiceName();
            }

            @Override
            public void onServiceUnregistered(NsdServiceInfo serviceInfo) {

            }
        };
    }

    /**
     * (3) 初始化发现监听器
     */
    public void initializeDiscoveryListener() {
        mDiscoveryListener = new NsdManager.DiscoveryListener() {
            @Override
            public void onStartDiscoveryFailed(String serviceType, int errorCode) {
                mNsdManager.stopServiceDiscovery(this);
            }

            @Override
            public void onStopDiscoveryFailed(String serviceType, int errorCode) {
                mNsdManager.stopServiceDiscovery(this);
                Log.d(TAG, "onStopDiscoveryFailed: " + serviceType);
            }

            @Override
            public void onDiscoveryStarted(String serviceType) {
                Log.d(TAG, "onDiscoveryStarted: " + serviceType);
            }

            @Override
            public void onDiscoveryStopped(String serviceType) {
                Log.d(TAG, "onDiscoveryStopped: " + serviceType);
            }

            @Override
            public void onServiceFound(NsdServiceInfo serviceInfo) {
                if (!serviceInfo.getServiceType().equals(NSD_SERVER_TYPE)) {
                    try {
                        mNsdManager.resolveService(serviceInfo, mResolverListener);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else if (serviceInfo.getServiceName().equals(NSD_SERVICE_NAME)) {
                    try {
                        mNsdManager.resolveService(serviceInfo, mResolverListener);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else if (serviceInfo.getServiceName().contains(NSD_SERVICE_NAME)) {
                    try {
                        mNsdManager.resolveService(serviceInfo, mResolverListener);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            @Override
            public void onServiceLost(NsdServiceInfo serviceInfo) {

            }
        };
    }

    /**
     * (4)初始化处理监听器
     */
    public void initializeResolveListener() {
        mResolverListener = new NsdManager.ResolveListener() {
            @Override
            public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {

            }

            @Override
            public void onServiceResolved(NsdServiceInfo serviceInfo) {
                if (serviceInfo.getServiceName().equals(mServerName)) {
                    Log.d(TAG, "Same IP.");
                    return;
                }
                mService = serviceInfo;
                Log.e(TAG, mService.toString());

                int port = mService.getPort();
                String serviceName = mService.getServiceName();
                InetAddress host = mService.getHost();
                String hostName = host.getHostName();
                String ip = host.getHostAddress();

                Log.e(TAG, "HostName: "+ hostName);
                Log.e(TAG, "Address:" + ip + ":" + port);
                Log.e(TAG, "ServiceName: "+serviceName);
                Log.e(TAG, "IP: " + bytesToIp(host.getAddress())+"");

                String mac = getMacByIp(ip);
                Log.d(TAG, "MAC: "+mac);
                if (mac.equals("")) {
                    discover(ip);
                }

                String[] items = {ip + ":" + port, hostName, serviceName, mac};
                sendMessage(0, items);

                readArpList();
            }
        };
    }

    /**
     * (5) 注销nsd服务
     */
    public void unInitService() {
        mNsdManager.unregisterService(mRegistrationListener);
        if (bListenerUsed) {
            mNsdManager.stopServiceDiscovery(mDiscoveryListener);
        }
    }

    private void showInfo(String hostAddress, String hostName, String serviceName, String mac) {
        TextView tvHostAddress = (android.widget.TextView)findViewById(R.id.tvHostAddress);
        tvHostAddress.setText(hostAddress);

        TextView tvHostName = (android.widget.TextView)findViewById(R.id.tvHostName);
        tvHostName.setText(hostName);

        TextView tvServiceName = (android.widget.TextView)findViewById(R.id.tvServiceName);
        tvServiceName.setText(serviceName);

        TextView tvMacAddress = (android.widget.TextView)findViewById(R.id.tvMacAddress);
        tvMacAddress.setText(mac);
    }
    private void showMac(String mac) {
        TextView tvMacAddress = (android.widget.TextView)findViewById(R.id.tvMacAddress);
        tvMacAddress.setText(mac);
    }


    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg == null) {
                return;
            }
            if (msg.what == 0) {
                String[] a = (String[]) msg.obj;
                showInfo(a[0], a[1], a[2], a[3]);
            } else if (msg.what == 1) {
                showMac((String)msg.obj);
            }
        }
    };

    private void sendMessage(int what, Object obj) {
        if (mHandler != null) {
            Message message = new Message();
            message.what = what;
            message.obj = obj;
            mHandler.sendMessage(message);
        }
    }


    public static void setServiceType(Context context, String serviceType) {
        SharedPreferences preferences = context.getSharedPreferences("config", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("ServiceType", serviceType);
        editor.commit();
    }

    public static String getServiceType(Context context) {
        SharedPreferences preferences = context.getSharedPreferences("config", Context.MODE_PRIVATE);
        return preferences.getString("ServiceType", "");
    }

    public static String bytesToIp(byte[] src) {
        return (src[0] & 0xff) + "." + (src[1] & 0xff) + "." + (src[2] & 0xff) + "." + (src[3] & 0xff);
    }


    private static final Pattern MAC_PATTERN = Pattern.compile("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})");
    private String getMacByIp(String ip) {
        try {
            Scanner s = new Scanner(Runtime.getRuntime().exec("arp -a " + ip).getInputStream()).useDelimiter("\\A");
            if (s.hasNext()) {
                String source = s.next();
                //Pattern pattern = Pattern.compile("([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})");
                Matcher matcher = MAC_PATTERN.matcher(source);
                if(matcher.find()) {
                    return matcher.group(0);
                }
                return "";
            } else {
                return "";
            }
        } catch (Exception e) {
            e.printStackTrace();
            try {
                BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp"));
                String line;
                while ((line = br.readLine()) != null) {
                    if (line.contains(ip)) {
                        Matcher matcher = MAC_PATTERN.matcher(line);
                        if(matcher.find()) {
                            return matcher.group(0);
                        }
                        return "";
                    }
                }
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return "";
        }
    }


    /**
     * https://blog.csdn.net/github_30662571/article/details/75088644
     */
    private void readArpList() {
        try {
            BufferedReader br = new BufferedReader(new FileReader("/proc/net/arp"));
            String line = "";
            String ip = "";
            String flag = "";
            String mac = "";

            while ((line = br.readLine()) != null) {
                try {
                    line = line.trim();
                    if (line.length() < 63) {
                        continue;
                    }
                    if (line.toUpperCase(Locale.US).contains("IP")) {
                        continue;
                    }
                    ip = line.substring(0, 17).trim();
                    flag = line.substring(29, 32).trim();
                    mac = line.substring(41, 63).trim();
                    if (mac.contains("00:00:00:00:00:00")) {
                        continue;
                    }
                    Log.e("scanner", "readArp: mac= "+mac+" ; ip= "+ip+" ;flag= "+flag);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            br.close();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    // 根据ip 网段去 发送arp 请求
    private void discover(String ip) {
        Thread ut = new UDPThread(ip);
        ut.start();
    }




    // UDPThread
    public class UDPThread extends Thread {
        private String target_ip = "";

        public final byte[] NETBIOS_REQUEST = {
                (byte) 0x82, (byte) 0x28, (byte) 0x0,  (byte) 0x0,  (byte) 0x0,
                (byte) 0x1,  (byte) 0x0,  (byte) 0x0,  (byte) 0x0,  (byte) 0x0,
                (byte) 0x0,  (byte) 0x0,  (byte) 0x20, (byte) 0x43, (byte) 0x4B,
                (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41,
                (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41,
                (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41,
                (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41,
                (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41,
                (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41, (byte) 0x41,
                (byte) 0x0,  (byte) 0x0,  (byte) 0x21, (byte) 0x0,  (byte) 0x1 };

        /**
         * 137端口的主要作用是在局域网中提供计算机的名字或IP地址查询服务
         */
        public static final short NBUDPP = 137;

        public UDPThread(String target_ip) {
            this.target_ip = target_ip;
        }

        @Override
        public synchronized void run() {
            if (target_ip == null || target_ip.equals("")) {
                return;
            }
            DatagramSocket socket = null;
            InetAddress address = null;
            DatagramPacket packet = null;
            try {
                address = InetAddress.getByName(target_ip);
                packet = new DatagramPacket(NETBIOS_REQUEST, NETBIOS_REQUEST.length, address, NBUDPP);
                socket = new DatagramSocket();
                socket.setSoTimeout(200);
                socket.send(packet);
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (socket != null) {
                    socket.close();
                }

                for (int i=0; i<10; i++) {
                    String mac = getMacByIp(target_ip);
                    if (!mac.equals("") && !mac.equals("00:00:00:00:00:00")) {
                        sendMessage(1, mac);
                        break;
                    }
                    try {
                        sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }
    }


}
